import type express from 'express'
import * as log4js from 'log4js'
import 'reflect-metadata'
import { AppLogger, MySqlLogger } from './Log4jHandler.ts'
import { MySqlHandler } from './MySqlHandler.ts'
import { type MysqlError } from 'mysql'
import * as path from 'path'
import { resolve } from 'path'
import { setUpSwaggerUi } from './SwaggerHandler.ts'
import { type Controller, InversifyExpressServer, TYPE } from 'inversify-express-utils'
import config from '@/config/application.ts'
import * as fs from 'fs'
import { Container } from 'inversify'
import cors from 'cors'
import * as bodyParser from 'body-parser'

export function ExpApplication (target: any) {
  const app = setupServer()

  // 处理接口文档
  setUpSwaggerUi(app)

  // 创建服务器监听
  const server = app.listen(config.port, config.hostname, () => {
    target.prototype.mainFn(server.address())
  })
}

// 初始化服务器
function setupServer () {
  // 装载IOC控制反转容器
  const container = new Container()

  // 扫描绑定控制器
  setupScanController(container)

  // 扫描实体
  setupScanModule(container)

  // 扫描服务
  setupScanService(container)

  // 处理数据库连接
  setupMySqlConnection(container)

  // 创建express服务器
  const server = new InversifyExpressServer(container)

  // 初始化服务器配置
  server.setConfig((app) => {
    app.use(bodyParser.urlencoded({
      extended: true
    }))
    app.use(bodyParser.json())

    // 处理express日志
    app.use(log4js.connectLogger(AppLogger, {
      // 自定义输出格式
      format: ':remote-addr :method :url :status :response-timems'
    }))

    // 允许跨域
    app.use(cors())
  })

  // 处理服务器抛出错误
  server.setErrorConfig((app) => {
    // eslint-disable-next-line n/handle-callback-err
    app.use((err: Error, request: express.Request, response: express.Response, next: express.NextFunction) => {
      AppLogger.error(err.stack)
      response.status(500).send('Something broke!')
    })
  })

  // 构建服务器
  return server.build()
}

// 处理数据库连接
function setupMySqlConnection (container: Container) {
  // 处理数据库连接池
  const MySql = new MySqlHandler()
  MySql.createMysqlConnection()
  MySql.Instance.connect((e: MysqlError) => {
    if (e !== null) {
      MySqlLogger.error(e)
    } else {
      MySqlLogger.info('数据库连接成功！')
    }
  })
}

// 扫描控制器
function setupScanController (container: Container) {
  config.scanControllerPaths.map(itemPath => {
    findFile(resolve(itemPath), (filePath: string) => {
      bindController(filePath, container)
    })
  })
}

// 扫描导入实体
function setupScanModule (container: Container) {
  config.scanModelPaths.map(itemPath => {
    findFile(resolve(itemPath), (filePath: string) => {
      bindModel(filePath, container)
    })
  })
}

// 扫描服务导入容器
function setupScanService (container: Container) {
  config.scanServicePaths.map(itemPath => {
    findFile(resolve(itemPath), (filePath: string) => {
      bindService(filePath, container)
    })
  })
}

// 绑定控制器
function bindController (filePath: string, container: Container) {
  // eslint-disable-next-line @typescript-eslint/no-var-requires
  const controller = require(filePath)
  const classObj: any = Object.values(controller)[0]
  AppLogger.info(classObj, filePath)
  // 请注意，您*必须*将所有控制器绑定到控制器
  container.bind<Controller>(TYPE.Controller).to(classObj).inSingletonScope().whenTargetNamed(filePath)
}

// 绑定实体
function bindModel (filePath: string, container: Container) {
  // eslint-disable-next-line @typescript-eslint/no-var-requires
  const module = require(filePath)
  const classObj: any = Object.values(module)[0]
  AppLogger.info(classObj, filePath)
  container.bind<typeof classObj>(Symbol.for(classObj.name)).to(classObj)
}

// 绑定服务
function bindService (filePath: string, container: Container) {
  if (!filePath.includes('.d.ts')) {
    // eslint-disable-next-line @typescript-eslint/no-var-requires
    const module = require(filePath)
    const classObj: any = Object.values(module)[0]
    AppLogger.info(classObj, filePath)
    container.bind<typeof classObj>(Symbol.for(classObj.name)).to(classObj)
  }
}

// 查找文件
function findFile (filePath: string, callback?: any) {
  function reFn (_filePath: string) {
    const files = fs.readdirSync(_filePath)

    for (const file of files) {
      const filePathJoin = path.join(_filePath, file)
      const stat = fs.statSync(filePathJoin)

      if (stat.isFile()) {
        // 如果是文件，则读取文件内容并进行处理
        callback?.(filePathJoin)
      } else if (stat.isDirectory()) {
        // 如果是子目录，则递归调用该方法
        reFn(filePathJoin)
      }
    }
  }
  reFn(filePath)
}
